#ifndef _CPARTICLE
#define _CPARTICLE

#include <utils.h>
#include <vector>
#include <console.h>
using namespace std;

extern console cons;

bool lt (double a, double b) {
  return (a < b? true : false);
}

bool gt (double a, double b) {
  return (a > b? true: false);
}

class CParticle {

	double xc, yc, zc; // current pos
	double xn, yn, zn; // next pos
	double r, g, b; // color
	
	
	int* p; // particles this particle affects
	static int ALLOC_PARTICLES;
	static int ALLOC_AFFECTS;
	
	static vector<int> opcodes;
	
	int* todo; // operation on each affected particle
	
	// for operation watch
	static const int MAX_WATCH_DISTANCE = 10;
	double* watch_distance;
	typedef bool (*watch_func)(double, double);
	watch_func* watches;
	
	

	void displace (int i, int dir) {

		double cx, cy, cz;
		PARTICLES[i].cur_pos (cx, cy, cz);

		double dx=0,dy=0,dz=0;
		findUnitVector (dx, dy, dz, xc, yc, zc, cx, cy, cz);

		double amount = dir * DISPLACEMENT;
		dx *= amount;
		dy *= amount;
		dz *= amount;

		xn += dx;
		yn += dy;
		zn += dz;

	}

	inline void destroy_affects () {
			if (p) delete[] p;
			if (todo) delete[] todo;
			if (watches) delete[] watches;
			if (watch_distance) delete[] watch_distance;
		}
		
	void create_affects () {
			p = new int [NUM_AFFECTS];
			todo = new int [NUM_AFFECTS];
			watches = new watch_func [NUM_AFFECTS];
			watch_distance = new double [NUM_AFFECTS];	
		}	

	public:

		enum {TOWARDS, AWAY, WATCH, NOTHING};
		const static double DISPLACEMENT = 0.040;
		
		static int NUM_PARTICLES;
		static int NUM_AFFECTS;
		
		static CParticle* PARTICLES;	
		
		inline static void destroy_particles () {
			if (PARTICLES) delete[] PARTICLES;
			ALLOC_PARTICLES = ALLOC_AFFECTS = 0;
		}
		
		static void create_particles (int np) {
			NUM_PARTICLES = np;
			if (NUM_PARTICLES > ALLOC_PARTICLES) {
				destroy_particles ();
				ALLOC_PARTICLES = NUM_PARTICLES;
				ALLOC_AFFECTS = NUM_AFFECTS;
				PARTICLES = new CParticle [NUM_PARTICLES];
			}
		}
		
		static void create_affects (int na) {
			//static int ALLOC_AFFECTS = 0;
			NUM_AFFECTS = na;
			if (NUM_AFFECTS > ALLOC_AFFECTS) {
				ALLOC_AFFECTS = NUM_AFFECTS;
				for (int i = 0; i < ALLOC_PARTICLES; ++i) {
					PARTICLES[i].destroy_affects ();
					PARTICLES[i].create_affects ();
				}
			}
			//create_particles (NUM_PARTICLES);
				
		}	
		
		static void op_towards () {
			opcodes.clear ();
			opcodes.push_back (TOWARDS);
			cons << "op: towards." << eol;
		}
		
		static void op_away () {
			opcodes.clear ();
			opcodes.push_back (AWAY);
			cons << "op: away." << eol;
		}
		
		static void op_towards_away () {
			opcodes.clear ();
			opcodes.push_back (TOWARDS);
			opcodes.push_back (AWAY);
			cons << "ops: towards, away" << eol;
		}
		
		static void op_all () {
			opcodes.clear ();
			opcodes.push_back (TOWARDS);
			opcodes.push_back (AWAY);
			opcodes.push_back (WATCH);
			cons << "ops: towards, away, watch" << eol;
		}
		
		static void op_towards_watch () {
			opcodes.clear ();
			opcodes.push_back (TOWARDS);
			opcodes.push_back (WATCH);
			cons << "ops: towards, watch" << eol;
		}
		
		static void op_away_watch () {
			opcodes.clear ();
			opcodes.push_back (AWAY);
			opcodes.push_back (WATCH);
			cons << "ops: away, watch" << eol;
		}	
		
		static string op_print () {
			string ops;
			for (int i = 0, j = opcodes.size(); i < j; ++i) {
				switch (opcodes[i]) {
					case TOWARDS:
						ops += "towards ";
						break;
					case AWAY:
						ops += "away ";
						break;
					case WATCH:
						ops += "watch ";
						break;
				}
			}
			return ops;
		}
		
		CParticle () {
			xc = yc = zc = 0;
			xn = yn = zn = 0;
			r = g = b = 0;
			p = todo = 0;
			watches = 0;
			watch_distance = 0;
			create_affects ();
		}
		
		~CParticle () {
			destroy_affects ();
		}

		void cur_pos (double& cx, double& cy, double& cz) {
			cx = xc; cy = yc; cz = zc;
		}

		void get_color (double& rr, double& gg, double& bb) {
			rr = r; gg = g; bb = b;
		}

		void eval () {
			for (int i = 0; i < NUM_AFFECTS; ++i) {
				int j = todo[i];
				int pi = p[i];
				switch (j) {
					case TOWARDS:
						displace (pi, 1);
					break;

					case AWAY:
						displace (pi, -1);
					break;
					
					/*
						case WATCH:
						not multithread safe because out of order pick() wont 
						reproduce sim upon reset.
					
						case WATCH:
							watch (pi, i);
						break;
					 
					*/

				}
			}
		}
		
		void eval_watch () {
			// called serially
			for (int i = 0; i < NUM_AFFECTS; ++i) {
				int j = todo[i];
				if (j == WATCH) {
					int pi = p[i];
					watch (pi, i);
				}
			}
		}

		void watch (int pi, int i) {

			double px, py, pz;
			PARTICLES[pi].cur_pos (px, py, pz);

			double vx, vy, vz;
			findVector (vx, vy, vz, xc, yc, zc, px, py, pz);
			double current_distance = findMagnitude (vx, vy, vz);
			if (watches[i](current_distance, watch_distance[i])) pick ();

		}

		void pick () {
			int num_ops = opcodes.size ();
			for (int i = 0; i < NUM_AFFECTS; ++i) {
				unsigned int r = getRand32 ();
				p[i] = r % NUM_PARTICLES;
				int j = r % num_ops;
				int op = opcodes[j];
				todo[i] = op;
				if (op == WATCH) {
					watch_distance [i] = r % MAX_WATCH_DISTANCE;
					if (r % 2) watches[i] = &lt; else watches[i] = &gt;
				}
			}
		}

		void init (const CBox<double>& bbox) {	

			const double inv = 1.0 / 0xffffffff;
			double rx = getRand32 () * inv;
			double ry = getRand32 () * inv;
			double rz = getRand32 () * inv;

			xn = xc = bbox.left + rx * (bbox.right - bbox.left);
			yn = yc = bbox.bottom + ry * (bbox.top - bbox.bottom);
			zn = zc = bbox.front + rz * (bbox.back - bbox.front);

			r = rx; g = ry; b = rz;

			pick ();

		}

	void draw_link (int l) {

		for (int i = 0; i < NUM_AFFECTS; ++i) {

			double px, py, pz;
			int pi = p[i];
			PARTICLES[pi].cur_pos (px, py, pz);

			int t = todo[i];

			if (t == l) {

				const double c = 0.5, dc = 0.25;
				double cr = dc, cg = dc, cb = dc;

				switch (t) {

					case TOWARDS:
					cg += c;
					break;

					case AWAY:
					cr += c;
					break;

					case WATCH:
					if (watches[i] == lt) {
						cr = 0; cg = 1; cb = 1;
					} else {
						cr = 1; cg = 1; cb = 0;
					}
					break;

				}

				glBegin (GL_LINES);

					// link
					glColor3f (cr, cg, cb);
					glVertex3f (xc, yc, zc);
					glVertex3f (px, py, pz);

					// from
					glColor3f (0, 1, 0);
					glVertex3f (xc, yc, zc);
					glVertex3f (xc, yc+dc,zc);

					// to
					glColor3f (1, 0, 0);
					glVertex3f (px, py, pz);
					glVertex3f (px, py-dc, pz);

				glEnd ();

			}
		}

	}

	void next () {
		xc = xn; yc = yn; zc = zn;
	}

};

#endif
